/*
* Copyright (c) 2024 China Unicom Digital Technology Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* Author: YuXiang Guo
* Date: 2025-08-18
 */

package report

import (
	"testing"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"openfuyao.com/colocation-management/pkg/colocation-manager/aggregate"
	"openfuyao.com/colocation-management/pkg/colocation-overquota-agent/metric"
)

// TestNewContainerUsageSamplesWithKey 测试newContainerUsageSamplesWithKey函数
func TestNewContainerUsageSamplesWithKey(t *testing.T) {
	now := time.Now()

	tests := []struct {
		name             string
		usage            *metric.NodeUsageReport
		expectedCount    int
		expectedWarnings int
		validateFunc     func([]*aggregate.ContainerUsageSampleWithKey) bool
	}{
		{
			name: "empty usage report",
			usage: &metric.NodeUsageReport{
				Containers: make(map[metric.PodRef]map[string]corev1.ResourceList),
				Timestamp:  metav1.NewTime(now),
			},
			expectedCount:    0,
			expectedWarnings: 0,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				return len(samples) == 0
			},
		},
		{
			name: "single container with CPU and memory",
			usage: &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "test-pod"): {
						"app": corev1.ResourceList{
							corev1.ResourceCPU:    *resource.NewMilliQuantity(500, resource.DecimalSI),
							corev1.ResourceMemory: *resource.NewQuantity(1024*1024*1024, resource.BinarySI),
						},
					},
				},
				Timestamp: metav1.NewTime(now),
			},
			expectedCount:    2,
			expectedWarnings: 0,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				if len(samples) != 2 {
					return false
				}

				cpuFound, memoryFound := false, false
				for _, sample := range samples {
					if sample.Resource == aggregate.ResourceCPU && sample.Usage == 500 {
						cpuFound = true
					}
					if sample.Resource == aggregate.ResourceMemory && sample.Usage == 1024*1024*1024 {
						memoryFound = true
					}
				}
				return cpuFound && memoryFound
			},
		},
		{
			name: "multiple containers",
			usage: &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "pod1"): {
						"app": corev1.ResourceList{
							corev1.ResourceCPU: *resource.NewMilliQuantity(200, resource.DecimalSI),
						},
					},
					metric.NewPodRef("default", "pod2"): {
						"sidecar": corev1.ResourceList{
							corev1.ResourceMemory: *resource.NewQuantity(512*1024*1024, resource.BinarySI),
						},
					},
				},
				Timestamp: metav1.NewTime(now),
			},
			expectedCount:    2,
			expectedWarnings: 0,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				return len(samples) == 2
			},
		},
		{
			name: "invalid resource type",
			usage: &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "test-pod"): {
						"app": corev1.ResourceList{
							corev1.ResourceStorage: *resource.NewQuantity(1024, resource.DecimalSI),
						},
					},
				},
				Timestamp: metav1.NewTime(now),
			},
			expectedCount:    0,
			expectedWarnings: 1,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				return len(samples) == 0
			},
		},
		{
			name: "mixed valid and invalid resources",
			usage: &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "test-pod"): {
						"app": corev1.ResourceList{
							corev1.ResourceCPU:     *resource.NewMilliQuantity(300, resource.DecimalSI),
							corev1.ResourceStorage: *resource.NewQuantity(1024, resource.DecimalSI),
						},
					},
				},
				Timestamp: metav1.NewTime(now),
			},
			expectedCount:    1,
			expectedWarnings: 1,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				return len(samples) == 1 && samples[0].Resource == aggregate.ResourceCPU
			},
		},
		{
			name: "CPU resource conversion",
			usage: &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "test-pod"): {
						"app": corev1.ResourceList{
							corev1.ResourceCPU: *resource.NewMilliQuantity(250, resource.DecimalSI),
						},
					},
				},
				Timestamp: metav1.NewTime(now),
			},
			expectedCount:    1,
			expectedWarnings: 0,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				return len(samples) == 1 && samples[0].Usage == 250
			},
		},
		{
			name: "memory resource conversion",
			usage: &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "test-pod"): {
						"app": corev1.ResourceList{
							corev1.ResourceMemory: *resource.NewQuantity(2048, resource.DecimalSI),
						},
					},
				},
				Timestamp: metav1.NewTime(now),
			},
			expectedCount:    1,
			expectedWarnings: 0,
			validateFunc: func(samples []*aggregate.ContainerUsageSampleWithKey) bool {
				return len(samples) == 1 && samples[0].Usage == 2048
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {

			samples := newContainerUsageSamplesWithKey(tt.usage)

			if len(samples) != tt.expectedCount {
				t.Errorf("Expected %d samples, got %d", tt.expectedCount, len(samples))
			}

			if tt.validateFunc != nil && !tt.validateFunc(samples) {
				t.Error("Custom validation function failed")
			}

			for _, sample := range samples {
				if sample.ContainerUsageSample.MeasureStart != tt.usage.Timestamp.Time {
					t.Errorf("MeasureStart mismatch: expected %v, got %v",
						tt.usage.Timestamp.Time, sample.ContainerUsageSample.MeasureStart)
				}

				if sample.Container.PodID.Namespace == "" || sample.Container.PodID.PodName == "" {
					t.Error("Container ID should have namespace and pod name")
				}

				if sample.Container.ContainerName == "" {
					t.Error("Container name should not be empty")
				}

				if sample.Resource != aggregate.ResourceCPU && sample.Resource != aggregate.ResourceMemory {
					t.Errorf("Invalid resource type: %v", sample.Resource)
				}
			}

			t.Logf("Test passed")
		})
	}
}

// TestPodRefConversion 测试PodRef转换错误处理
func TestPodRefConversion(t *testing.T) {

	usage := &metric.NodeUsageReport{
		Containers: map[metric.PodRef]map[string]corev1.ResourceList{
			metric.NewPodRef("default", ""): {
				"app": corev1.ResourceList{
					corev1.ResourceCPU: *resource.NewMilliQuantity(100, resource.DecimalSI),
				},
			},
		},
		Timestamp: metav1.NewTime(time.Now()),
	}

	samples := newContainerUsageSamplesWithKey(usage)
	if len(samples) != 0 {
		t.Errorf("Expected 0 samples for invalid PodRef, got %d", len(samples))
	}
}

// TestResourceQuantityEdgeCases 测试资源数量的边界情况
func TestResourceQuantityEdgeCases(t *testing.T) {
	tests := []struct {
		name           string
		cpuQuantity    *resource.Quantity
		memoryQuantity *resource.Quantity
		expectedCPU    aggregate.ResourceAmount
		expectedMemory aggregate.ResourceAmount
	}{
		{
			name:           "zero values",
			cpuQuantity:    resource.NewMilliQuantity(0, resource.DecimalSI),
			memoryQuantity: resource.NewQuantity(0, resource.DecimalSI),
			expectedCPU:    0,
			expectedMemory: 0,
		},
		{
			name:           "large values",
			cpuQuantity:    resource.NewMilliQuantity(10000, resource.DecimalSI),
			memoryQuantity: resource.NewQuantity(1024*1024*1024*10, resource.BinarySI),
			expectedCPU:    10000,
			expectedMemory: 1024 * 1024 * 1024 * 10,
		},
		{
			name:           "fractional CPU",
			cpuQuantity:    resource.NewMilliQuantity(123, resource.DecimalSI),
			memoryQuantity: resource.NewQuantity(123456789, resource.DecimalSI),
			expectedCPU:    123,
			expectedMemory: 123456789,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			usage := &metric.NodeUsageReport{
				Containers: map[metric.PodRef]map[string]corev1.ResourceList{
					metric.NewPodRef("default", "test-pod"): {
						"app": corev1.ResourceList{
							corev1.ResourceCPU:    *tt.cpuQuantity,
							corev1.ResourceMemory: *tt.memoryQuantity,
						},
					},
				},
				Timestamp: metav1.NewTime(time.Now()),
			}

			samples := newContainerUsageSamplesWithKey(usage)

			if len(samples) != 2 {
				t.Errorf("Expected 2 samples, got %d", len(samples))
				return
			}

			var cpuSample, memorySample *aggregate.ContainerUsageSampleWithKey
			for _, sample := range samples {
				if sample.Resource == aggregate.ResourceCPU {
					cpuSample = sample
				} else if sample.Resource == aggregate.ResourceMemory {
					memorySample = sample
				}
			}

			if cpuSample != nil && cpuSample.Usage != tt.expectedCPU {
				t.Errorf("CPU usage mismatch: expected %d, got %d", tt.expectedCPU, cpuSample.Usage)
			}

			if memorySample != nil && memorySample.Usage != tt.expectedMemory {
				t.Errorf("Memory usage mismatch: expected %d, got %d", tt.expectedMemory, memorySample.Usage)
			}
		})
	}
}

// BenchmarkNewContainerUsageSamplesWithKey 性能基准测试
func BenchmarkNewContainerUsageSamplesWithKey(b *testing.B) {
	usage := &metric.NodeUsageReport{
		Containers: map[metric.PodRef]map[string]corev1.ResourceList{
			metric.NewPodRef("default", "pod1"): {
				"container1": corev1.ResourceList{
					corev1.ResourceCPU:    *resource.NewMilliQuantity(100, resource.DecimalSI),
					corev1.ResourceMemory: *resource.NewQuantity(1024*1024*1024, resource.BinarySI),
				},
				"container2": corev1.ResourceList{
					corev1.ResourceCPU:    *resource.NewMilliQuantity(200, resource.DecimalSI),
					corev1.ResourceMemory: *resource.NewQuantity(512*1024*1024, resource.BinarySI),
				},
			},
			metric.NewPodRef("default", "pod2"): {
				"app": corev1.ResourceList{
					corev1.ResourceCPU:    *resource.NewMilliQuantity(300, resource.DecimalSI),
					corev1.ResourceMemory: *resource.NewQuantity(256*1024*1024, resource.BinarySI),
				},
			},
		},
		Timestamp: metav1.NewTime(time.Now()),
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = newContainerUsageSamplesWithKey(usage)
	}
}
